#define _GNU_SOURCE

#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h> 
#include <errno.h> 
#include <string.h> 
#include <signal.h> 

typedef struct uart_hardware_cfg
{
    unsigned int baudrate;      /* 波特率 */ 
    unsigned char dbit;         /* 数据位 */ 
    char parity;                /* 奇偶校验 */ 
    unsigned char sbit;         /* 停止位 */ 
}uart_cfg_t;

static struct termios old_cfg;  //用于保存终端的原有的配置参数 
static int fd;      //串口终端对应的文件描述符 

static int uart_init(const char *device)
{
    //打开终端设备
    fd = open("/dev/ttySTM2",O_RDWR | O_NOCTTY);    //O_NOCTTY,不会成为进程的控制终端
    if(fd <0) {
        fprintf(stderr, "open error:%s: %s\n", device, strerror(errno)); 
        close(fd); 
        return -1; 
    }

    //获取串口当前的配置参数 
    if (0 > tcgetattr(fd, &old_cfg)) { 
        fprintf(stderr, "tcgetattr error: %s\n", strerror(errno)); 
        close(fd); 
        return -1; 
    } 
    return 0; 
}


static int uart_cfg(const uart_cfg_t *cfg) 
{
    struct termios new_cfg = {0};   //将new_cfg对象清零 
    speed_t speed; 

    /* 设置为原始模式 */ 
    cfmakeraw(&new_cfg); 
    /* 使能接收 */ 
    new_cfg.c_cflag |= CREAD; 
    /* 设置波特率 */ 
    speed = B115200; 
    printf("default baud rate: 115200\n"); 
    if (0 > cfsetspeed(&new_cfg, speed)) { 
        fprintf(stderr, "cfsetspeed error: %s\n", strerror(errno)); 
        return -1; 
    } 

    /* 设置数据位大小 */ 
    new_cfg.c_cflag &= ~CSIZE;  //将数据位相关的比特位清零
    //默认数据位大小为8
    new_cfg.c_cflag |= CS8; 
    printf("default data bit size: 8\n"); 

    /* 设置奇偶校验 */ 
    //默认配置为无校验 
    new_cfg.c_cflag &= ~PARENB; 
    new_cfg.c_iflag &= ~INPCK; 
    printf("default parity: N\n"); 

    /* 设置停止位 */ 
    //默认配置为1个停止位 
    new_cfg.c_cflag &= ~CSTOPB; 
    printf("default stop bit size: 1\n");

    /* 将MIN和TIME设置为0 */ 
    //有数据就读取，无阻塞
    new_cfg.c_cc[VTIME] = 0; 
    new_cfg.c_cc[VMIN] = 0; 

    /* 清空缓冲区 */ 
    if (0 > tcflush(fd, TCIOFLUSH)) { 
        fprintf(stderr, "tcflush error: %s\n", strerror(errno)); 
        return -1; 
    } 

    /* 写入配置、使配置生效 */ 
    if (0 > tcsetattr(fd, TCSANOW, &new_cfg)) { 
        fprintf(stderr, "tcsetattr error: %s\n", strerror(errno)); 
        return -1; 
    } 

    return 0;
}

static void show_help(const char *app) 
{ 
    printf("Usage: %s [选项]\n"
        "\n必选选项:\n"
        "  --dev=DEVICE     指定串口终端设备名称, 譬如--dev=/dev/ttymxc2\n" 
        "  --type=TYPE      指定操作类型, 读串口还是写串口, 譬如--type=read(read表示读、write表示 \
写、其它值无效)\n"  
        "  --help           查看本程序使用帮助信息\n\n", app); 
} 

/** 
 ** 信号处理函数，当串口有数据可读时，会跳转到该函数执行 
 **/ 
static void io_handler(int sig, siginfo_t *info, void *context) 
{ 
    unsigned char buf[10] = {0}; 
    int ret; 
    int n; 
 
    if(SIGRTMIN != sig) 
        return; 
 
    /* 判断串口是否有数据可读 */ 
    if (POLL_IN == info->si_code) { 
        ret = read(fd, buf, 8);     //一次最多读8个字节数据 
        printf("[ "); 
        for (n = 0; n < ret; n++) 
            printf("0x%hhx ", buf[n]); 
        printf("]\n"); 
    } 
}

/** 
 ** 异步I/O初始化函数 
 **/ 
static void async_io_init(void) 
{ 
    struct sigaction sigatn; 
    int flag; 
 
    /* 使能异步I/O */ 
    flag = fcntl(fd, F_GETFL);  //使能串口的异步I/O功能 
    flag |= O_ASYNC | O_NONBLOCK; 
    if (fcntl(fd, F_SETFL, flag) == -1) {
        perror("fcntl F_SETFL failed");
        exit(1);
    } 
 
    /* 设置异步I/O的所有者 */ 
    fcntl(fd, F_SETOWN, getpid()); 

    /* 指定实时信号SIGRTMIN作为异步I/O通知信号 */ 
    fcntl(fd, F_SETSIG, SIGRTMIN); 
 
    /* 为实时信号SIGRTMIN注册信号处理函数 */ 
    sigatn.sa_sigaction = io_handler;  //当串口有数据可读时，会跳转到io_handler函数 
    sigatn.sa_flags = SA_SIGINFO; 
    sigemptyset(&sigatn.sa_mask);

    if (sigaction(SIGRTMIN, &sigatn, NULL) == -1) {
        perror("sigaction failed");
        exit(1);
    }
} 


int main(int argc,char **argv)
{
    uart_cfg_t cfg = {0}; 
    char *device = NULL; 
    int rw_flag = -1; 
    unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44, 
                0x55, 0x66, 0x77, 0x88};    //通过串口发送出去的数据 
    int n;

    /* 解析出参数 */ 
    for (n = 1; n < argc; n++) { 
 
        if (!strncmp("--dev=", argv[n], 6)) 
            device = &argv[n][6]; 
        else if (!strncmp("--brate=", argv[n], 8)) 
            cfg.baudrate = atoi(&argv[n][8]); 
        else if (!strncmp("--dbit=", argv[n], 7)) 
            cfg.dbit = atoi(&argv[n][7]); 
        else if (!strncmp("--parity=", argv[n], 9)) 
            cfg.parity = argv[n][9]; 
        else if (!strncmp("--sbit=", argv[n], 7)) 
            cfg.sbit = atoi(&argv[n][7]); 
        else if (!strncmp("--type=", argv[n], 7)) { 
            if (!strcmp("read", &argv[n][7])) 
                rw_flag = 0;        //读 
            else if (!strcmp("write", &argv[n][7])) 
                rw_flag = 1;        //写 
        } 
        else if (!strcmp("--help", argv[n])) { 
            show_help(argv[0]); //打印帮助信息 
            exit(EXIT_SUCCESS); 
        } 
    } 

    if (NULL == device || -1 == rw_flag) { 
        fprintf(stderr, "Error: the device and read|write type must be set!\n"); 
        show_help(argv[0]); 
        exit(EXIT_FAILURE); 
    } 

    /* 串口初始化 */ 
    if (uart_init(device)) 
        exit(EXIT_FAILURE); 
 
    /* 串口配置 */ 
    if (uart_cfg(&cfg)) { 
        tcsetattr(fd, TCSANOW, &old_cfg);   //恢复到之前的配置 
        close(fd); 
        exit(EXIT_FAILURE); 
    } 

    /* 读|写串口 */
    switch (rw_flag) { 
    case 0:  //读串口数据 
        async_io_init(); //我们使用异步I/O方式读取串口的数据，调用该函数去初始化串口的异步I/O 
        printf("set read success!\n");
        for ( ; ; ) 
            sleep(1);    //进入休眠、等待有数据可读，有数据可读之后就会跳转到io_handler()函数 
        break; 
    case 1:   //向串口写入数据 
        for ( ; ; ) {     //循环向串口写入数据 
            write(fd, w_buf, 8);  //一次向串口写入8个字节 
            sleep(1);         //间隔1秒钟 
        } 
        break; 
    }

    /* 退出 */ 
    tcsetattr(fd, TCSANOW, &old_cfg);   //恢复到之前的配置 
    close(fd); 
    exit(EXIT_SUCCESS); 
}

